import types
from sgmllib import SGMLParser
import htmlentitydefs
from __init__ import FORMAT_TYPES, UnknownMicroformatError

class BaseMicroformatProcessor(SGMLParser, object):
    """
    This is based on the BaseHTMLProcessor class from Dive Into Python by Mark Pilgrim
    """
    def __init__(self, mf):
        SGMLParser.__init__(self)
        self.microformat = mf
        self.pieces = []
        self.document_tree = []
        self.property_tree = []
        self.current_tag = None
        self.current_property = None
        self.in_microformat = False
        self.count = 0
        self.verbose = False
    
    def create_new_node(self):
        return {'node': [], 'children': [], 'parent': None}
    
    def get_property_value_from_attributes(self, attr, attrs):
        """
        Retrieves the value of a microformat property from the attribute of
        a tag (given the attributes, not the tag!)
        
        Arguments:
            attr    the name of the attribute
            attrs   the list of attributes
        """
        for a in attrs:
            if a[0] == attr:
                return a[1]
    
    def handle_charref(self, ref):
        self.pieces.append("&#%(ref)s;" % locals())
    
    def handle_comment(self, text):
        self.pieces.append("<!--%(text)s-->" % locals())
    
    def handle_data(self, data):
        """
        Received some plain data
        
        Checks if the data node may belong to a microformat property
        """
        self.pieces.append(data)
        if self.in_microformat:
            # if len(self.property_tree) > 0:
            if self.current_property:
                # get the most recent value in the property tree
                # prop = self.property_tree[len(self.property_tree) - 1]['node']
                prop = self.current_property['node']
                
                # properties in the tree may be lists or strings (lists when
                # there is more than one property on a single node like
                # <div class="fn org">)
                if type(prop) is types.StringType:
                    # this is just a single property, but let's wrap
                    # it in a list for processing
                    prop = [prop]
                for p in prop:
                    if len(self.tree) > 0 and self.microformat.property_uses_data(p, self.tree[len(self.tree) - 1]):
                        # determine if this node has a parent
                        parent = None
                        # if this node has a parent then we should look up to that guy
                        if self.current_property['parent']:
                            parent = self.current_property['parent']
                        self.microformat.set_property(p, data, parent)
    
    def handle_decl(self, text):
        self.pieces.append("<!%(text)s>" % locals())
    
    def handle_entityref(self, ref):
        self.pieces.append("&%(ref)s" % locals())
        if ref in htmlentitydefs.entitydefs:
            self.pieces.append(";")
    
    def handle_pi(self, text):
        self.pieces.append("<?%(text)s>" % locals())
    
    def node_is_microformat(self, attrs):
        """
        This method displays whether or not this node in the XML is a microformat. This is
        overwritten by the concrete parsers
        """
        return False
    
    def reset(self):
        """
        Reset the internals of this object
        """
        self.pieces = []
        self.current_tag = None
        self.current_property = None
        self.tree = []
        self.in_microformat = False
        self.property_tree = []
        self.count = 0
        SGMLParser.reset(self)
    
    def unknown_starttag(self, tag, attrs):
        """
        Process the current tag
        """
        if self.verbose: print "unknown_starttag: %s %s (%s)" % (tag, attrs, self.in_microformat)
        
        # if this is a new tag, update current_tag and the tree (note that this
        # ignores self-contained tags (e.g., img, br, etc))
        if tag != self.current_tag and self.get_starttag_text()[-2:] != "/>":
            self.current_tag = tag
            self.tree.append(tag)
            self.document_tree.append(tag)
        
        # create a list of the attributes as a string and append it to the "pieces" bit
        strattrs = "".join([' %s="%s"' % (key, value) for key, value in attrs])
        self.pieces.append("<%(tag)s%(strattrs)s>" % locals())
        
        if not self.in_microformat and self.node_is_microformat(attrs):
            if self.verbose: print "unknown_starttag: start microformat (%s %s)" % (tag, attrs)
            self.in_microformat = True
            self.count = self.count + 1
        
        if self.in_microformat:
            # if self.verbose: print "unknown_starttag: in_microformat %s %s" % (tag, attrs)
            # in the microformat, so pay attention for nodes that we need to capture by examining the
            # attributes that are passed in for properties
            for attr in attrs:
                # !!! this will have to change eventually to handle non-class based properties
                if attr[0] != 'class': continue
                classes = attr[1].split(' ')
                properties = self.create_new_node()
                sub = False
                for c in classes:
                    if c in self.microformat.properties['required'] or c in self.microformat.properties['optional']:
                        # this is a property that we need to pay attention to so add
                        # it to the property_tree
                        # print "current: %s; new: %s" % (self.current_property, c)
                        # if self.verbose:
                        #     print "unknown_starttag: c: %s" % c
                        #     print "\tcurrent_property: %s" % self.current_property
                        if self.current_property and len(self.property_tree) > 0:
                            sub = True
                            parent_property = self.current_property
                            # if self.verbose:
                            #     print "\tparent_property: %s" % parent_property
                        self.current_property = properties
                        properties['node'].append(c)
                        self.microformat.set_property_attribute(c, "tag", tag)
                        # see if this property has any tag rules that apply to this tag
                        if self.microformat.property_has_tag_rule(c, tag):
                            rule = self.microformat.get_property(c).tag_rules[tag]
                            if type(rule) is types.StringType:
                                value = self.get_property_value_from_attributes(rule, attrs)
                                self.microformat.set_property(c, value)
                
                if len(properties['node']) > 0:
                    if not sub:
                        self.property_tree.append(properties)
                    else:
                        if self.verbose: 
                            print "unknown_starttag: %s adding to child" % tag
                            # print "\tproperty_tree: %s" % self.property_tree
                            # print "\tparent_property: %s" % parent_property['children']
                        properties['parent'] = parent_property
                        parent_property['children'].append(properties)
                        # if self.verbose:
                        #     print "\tproperty_tree (after): %s" % self.property_tree
        if tag in ['img', 'hr', 'br', 'link']:
            self.unknown_endtag(tag)
    
    def unknown_endtag(self, tag):
        """
        Handle end tags
        """
        # add the end tag to the pieces
        if self.verbose: 
            print "unknown_endtag: %s (in_microformat: %s; current_property: %s)" % (tag, self.in_microformat, self.current_property != None)
        self.pieces.append("</%(tag)s>" % locals())
        if len(self.tree) > 0:
            # there is a tag in the tag tree, so get rid of it as
            # we have now left this tag
            self.tree.pop()
        if self.current_property and self.current_property['parent']:
            if self.verbose: print "\tcurrent_property: %s" % self.current_property['parent']
            self.current_property = self.current_property['parent']
            if self.verbose:
                print "\tnew current_property: %s" % self.current_property
        elif self.current_property and not self.current_property['parent']:
            self.current_property = None
        elif not self.current_property:
            if self.verbose and self.in_microformat:
                print "\tleft microformat"
            self.in_microformat = False
            self.current_property = None
            self.property_tree = []
    
    def output(self):
        """Return processed HTML as a single string"""
        return "".join(self.pieces)

class MicroformatParser(object):
    """
    Parser 'factory' class for all microformats.
    
    This can either be created with a single or a set of
    microformats in mind or with it will default to trying all
    formats.
    
    (This is new and is still being implemented)
    """
    
    def __init__(self, parser=[]):
        """
        The constructor for the microformat parser.
        
        The only argument for this method is a list of microformat
        types that should be parsed. By default this will pull the list of
        available formats in the module.
        """
        self.count = 0
        self.groups = []
        self.verbose = False
        
        if type(parser) is not types.ListType:
            raise ValueError('Parser should be a list; received %s: %s' % (type(parser), parser))
        
        # validate the microformats that were passed in
        if len(parser):
            for t in parser:
                if FORMAT_TYPES.count(t) != 1:
                    raise UnknownMicroformatError('Invalid format type in parser types: %s' % t)
            self.types = parser
        else:
            self.types = FORMAT_TYPES
    
    def group(self, num):
        """
        Get the requested microformat parser
        
        """
        if len(self.groups) < num or num < 0:
            raise IndexError('Invalid group number %d' % num)
        return self.groups[num]
    
    def parse(self, data):
        """
        Parse the requested date
        
        """
        for t in self.types:
            # create the parser for this format type
            l = "%s%s" % (t[0:2].upper(), t[2:])
            
            # import the microformat class
            format = __import__(t, globals(), locals(), [l])
            parser = getattr(format, l)
            
            # create the parser instance
            if self.verbose:
                print "parse: created parser %s" % l
            p = parser()
            if self.verbose:
                p.verbose = True
            p.parse(data)
            
            # update the count
            self.count += p.count()
            self.groups.append(p)
